import {FocusElement} from 'tui-lib/ui/primitives'

import * as ansi from 'tui-lib/util/ansi'
import telc from 'tui-lib/util/telchars'
import unic from 'tui-lib/util/unichars'

export default class TextInput extends FocusElement {
  // An element that the user can type in.

  constructor() {
    super()

    this.value = ''
    this.cursorVisible = true
    this.cursorIndex = 0
    this.scrollChars = 0
  }

  drawTo(writable) {
    // There should be room for the cursor so move the "right edge" left a
    // single character.

    const startRange = this.scrollChars
    const endRange = this.scrollChars + this.w - 3

    let str = this.value.slice(startRange, endRange)

    writable.write(ansi.moveCursor(this.absTop, this.absLeft + 1))
    writable.write(str)

    // Ellipsis on left side, if there's more characters behind the visible
    // area.
    if (startRange > 0) {
      writable.write(ansi.moveCursor(this.absTop, this.absLeft))
      writable.write(unic.ELLIPSIS)
    }

    // Ellipsis on the right side, if there's more characters ahead of the
    // visible area.
    if (endRange < this.value.length) {
      writable.write(ansi.moveCursor(this.absTop, this.absRight - 1))
      writable.write(unic.ELLIPSIS.repeat(2))
    }

    this.cursorX = this.cursorIndex - this.scrollChars + 1

    super.drawTo(writable)
  }

  keyPressed(keyBuf) {
    try {
      if (keyBuf[0] === 127) {
        this.value = (
          this.value.slice(0, this.cursorIndex - 1) +
          this.value.slice(this.cursorIndex)
        )
        this.cursorIndex--
        this.root.cursorMoved()
        return false
      } else if (keyBuf[0] === 13) {
        // These are aliases for each other.
        this.emit('value', this.value)
        this.emit('confirm', this.value)
      } else if (keyBuf[0] === 0x1b && keyBuf[1] === 0x5b) {
        // Keyboard navigation
        if (keyBuf[2] === 0x44) {
          this.cursorIndex--
          this.root.cursorMoved()
        } else if (keyBuf[2] === 0x43) {
          this.cursorIndex++
          this.root.cursorMoved()
        }
        return false
      } else if (telc.isEscape(keyBuf)) {
        // ESC is bad and we don't want that in the text input!
        // Also emit a "cancel" event, which doesn't necessarily do anything,
        // but can be listened to.
        this.emit('cancel')
      } else {
        const isTextInput = keyBuf.toString().split('').every(chr => {
          const n = chr.charCodeAt(0)
          return n > 31 && n < 127
        })

        if (isTextInput) {
          this.value = (
            this.value.slice(0, this.cursorIndex) + keyBuf.toString() +
            this.value.slice(this.cursorIndex)
          )
          this.cursorIndex += keyBuf.toString().length
          this.root.cursorMoved()
          this.emit('change', this.value)

          return false
        }
      }
    } finally {
      this.keepCursorInRange()
    }
  }

  setValue(value) {
    this.value = value
    this.moveToEnd()
  }

  moveToEnd() {
    this.cursorIndex = this.value.length
    this.keepCursorInRange()
  }

  keepCursorInRange() {
    // Keep the cursor inside or at the end of the input value.

    if (this.cursorIndex < 0) {
      this.cursorIndex = 0
    }

    if (this.cursorIndex > this.value.length) {
      this.cursorIndex = this.value.length
    }

    // Scroll right, if the cursor is past the right edge of where text is
    // displayed.
    while (this.cursorIndex - this.scrollChars > this.w - 3) {
      this.scrollChars++
    }

    // Scroll left, if the cursor is behind the left edge of where text is
    // displayed.
    while (this.cursorIndex - this.scrollChars < 0) {
      this.scrollChars--
    }

    // Scroll left, if we can see past the end of the text.
    while (this.scrollChars > 0 && (
      this.scrollChars + this.w - 3 > this.value.length)
    ) {
      this.scrollChars--
    }
  }

  get value() { return this.getDep('value') }
  set value(v) { return this.setDep('value', v) }
  get cursorIndex() { return this.getDep('cursorIndex') }
  set cursorIndex(v) { return this.setDep('cursorIndex', v) }
}
